<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>俄罗斯方块</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            line-height: 0;
        }

        html, body {
            height: 100%;
            overflow: hidden
        }

        #gc {
            overflow: hidden;
            padding-right: 1px;
        }

        #gc > div {
            float: left;
            margin-left: 1px;
            margin-top: 1px;
        }
    </style>
    <script>
        function Game(gc) {
            var self = this;
            var timer;
            var colors = {
                0: "",
                1: "brown",
                2: "cornflowerblue",
                3: "cadetblue",
                4: "darkgreen",
                5: "coral",
                6: "goldenrod",
                7: "mediumorchid"
            };
            var pixels = [];
            var gcw = gc.offsetWidth;
            var gch = gc.offsetHeight;
            var rowNum = 13;
            var blockW = (gcw - 1 - rowNum) / rowNum;
            var columnNum = Math.floor(gch / blockW) - 1;
            var html = "";
            for (var x = 0; x < rowNum; x++) {
                pixels[x] = new Array(columnNum);
                for (var y = 0; y < pixels[x].length; y++) {
                    html += "<div style='width:" + blockW + "px;height:" + blockW + "px' ></div>";
                    pixels[x][y] = 0;
                }
            }
            gc.innerHTML = html;
            var divs = gc.getElementsByTagName("div");
            var currBlock = null;

            var isGameOver = function () {
                var pos = currBlock.pos;
                clearPixel(pos);
                var isOver = false;
                for (var i in pos) {
                    if (pixels[pos[i].x][pos[i].y + 1] > 0) {
                        isOver = true;
                        break;
                    }
                }
                if (isOver) {
                    isOver = false;
                    for (var i2 in pos) {
                        if (pos[i2].y < 0) {
                            isOver = true;
                            break;
                        }
                    }
                }
                setPixel(pos, currBlock.color);
                return isOver;
            }
            //消除
            var eliminate = function (pos) {
                var max = -1;
                for (var i in pos) {
                    if (max < pos[i].y) {
                        max = pos[i].y;
                    }
                }
                for (var y = max - 1; y >= 0; y--) {
                    var pass = true;
                    for (var x = 0; x < rowNum; x++) {
                        if (pixels[x][y] === 0) {
                            pass = false;
                            break;
                        }
                    }
                    if (pass) {
                        for (var y2 = y; y2 >= 0; y2--) {
                            for (var x2 = 0; x2 < rowNum; x2++) {
                                if (y2 > 0) {
                                    pixels[x2][y2] = pixels[x2][y2 - 1];
                                } else {
                                    pixels[x2][y2] = 0;
                                }
                            }
                        }
                    }
                }
            }
            //旋转方块
            this.rotate = function () {
                clearPixel(currBlock.pos);
                var rs = setAppear(currBlock.rotate + 1, currBlock.pos[0]);
                if (isCanUse(rs.npos)) {
                    currBlock.pos = rs.npos;
                    currBlock.rotate = rs.type;
                    setPixel(currBlock.pos, currBlock.color);
                    paint();
                } else {
                    setPixel(currBlock.pos, currBlock.color);
                }
            };

            //移动活动方块，direct:1=下,2=左,3=右,4=落底
            this.move = function (direct, isPaint) {
                clearPixel(currBlock.pos);
                var npos = newPos(currBlock.pos, direct);
                if (direct === 4) {
                    var bak = null;
                    while (isCanUse(npos)) {
                        bak = npos;
                        npos = newPos(npos, 1);
                    }
                    if (bak != null) {
                        npos = bak;
                    }
                }
                var rs = isCanUse(npos);
                if (rs === true) {
                    currBlock.pos = npos;
                }
                setPixel(currBlock.pos, currBlock.color);
                if (direct !== 1 || isPaint === true) {
                    paint();
                }
                if (direct === 4 || isPaint === true) {
                    clearInterval(timer);
                    paintTimer();
                }
                if ((rs === false && direct === 1) || direct === 4) {
                    if (isGameOver()) {
                        clearInterval(timer);
                        alert("游戏结束!");
                    } else {
                        eliminate();
                        newBlock();
                    }
                }

            };
            var setPixel = function (pos, color) {
                for (var i in pos) {
                    if (pos[i].x >= 0 && pos[i].y >= 0) {
                        pixels[pos[i].x][pos[i].y] = color;
                    }
                }
            }
            var clearPixel = function (pos) {
                for (var i in pos) {
                    if (pos[i].x >= 0 && pos[i].y >= 0) {
                        pixels[pos[i].x][pos[i].y] = 0;
                    }
                }
            }
            var paint = function () {
                for (var x = 0; x < pixels.length; x++) {
                    for (var y = 0; y < pixels[x].length; y++) {
                        if (x >= 0 && y >= 0) {
                            divs[y * rowNum + x].style.backgroundColor = colors[pixels[x][y]];
                        }
                    }
                }
            };
            var paintTimer = function () {
                timer = setInterval(function () {
                    self.move(1);
                    paint();
                }, 500);
            }
            //验证指定的位置是否可用
            var isCanUse = function (pos) {
                for (var i in pos) {
                    if (pos[i].x < 0 || pos[i].x >= rowNum) {
                        return false;
                    }
                    if (pos[i].y < 0) {
                        continue;
                    }
                    if (pos[i].y >= columnNum) {
                        return false;
                    }
                    if (pixels[pos[i].x][pos[i].y] > 0) {
                        return false;
                    }
                }
                return true;
            };

            //返回指定方向移动一位的新位置
            var newPos = function (cpos, direct) {
                var npos = new Array(cpos.length);
                for (var i in cpos) {
                    switch (direct) {
                        //向下
                        case 1:
                        case 4:
                            npos[i] = {x: cpos[i].x, y: cpos[i].y + 1};
                            break
                        case 2://向左
                            npos[i] = {x: cpos[i].x - 1, y: cpos[i].y};
                            break
                        case 3://向右
                            npos[i] = {x: cpos[i].x + 1, y: cpos[i].y};
                            break
                    }
                }
                return npos;
            };

            var newBlock = function () {
                var idx = Math.floor(Math.random() * 7) + 1;
                var type = idx * 10 + 1;
                currBlock = {
                    type: type,
                    rotate: type,
                    color: idx,
                    pos: setAppear(type, {x: Math.floor(rowNum / 2), y: -1}).npos
                };
            };

            //基于指定的位置计算指定方块的外形,第一个位置固定是锚定点
            var setAppear = function (type, p) {
                var npos;
                // 一
                if (type > 10 && type < 20) {
                    type = type > 12 ? 11 : type;
                    if (type === 11) {
                        npos = [{x: p.x, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x + 2, y: p.y}];
                    } else if (type === 12) {
                        npos = [{x: p.x, y: p.y}, {x: p.x, y: p.y + 1}, {x: p.x, y: p.y - 1}, {x: p.x, y: p.y - 2}];
                    }
                }
                // 田
                if (type > 20 && type < 30) {
                    npos = [{x: p.x, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x + 1, y: p.y + 1}, {x: p.x, y: p.y + 1}];
                }
                // |___
                if (type > 30 && type < 40) {
                    type = type > 34 ? 31 : type;
                    if (type === 31) {
                        npos = [{x: p.x, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x - 1, y: p.y - 1}];
                    } else if (type === 32) {
                        npos = [{x: p.x, y: p.y}, {x: p.x, y: p.y + 1}, {x: p.x, y: p.y - 1}, {x: p.x + 1, y: p.y - 1}];
                    } else if (type === 33) {
                        npos = [{x: p.x, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x + 1, y: p.y + 1}];
                    } else if (type === 34) {
                        npos = [{x: p.x, y: p.y}, {x: p.x, y: p.y - 1}, {x: p.x, y: p.y + 1}, {x: p.x - 1, y: p.y + 1}];
                    }
                }
                // ___|
                if (type > 40 && type < 50) {
                    type = type > 44 ? 41 : type;
                    if (type === 41) {
                        npos = [{x: p.x, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x + 1, y: p.y - 1}];
                    } else if (type === 42) {
                        npos = [{x: p.x, y: p.y}, {x: p.x, y: p.y - 1}, {x: p.x, y: p.y + 1}, {x: p.x + 1, y: p.y + 1}];
                    } else if (type === 43) {
                        npos = [{x: p.x, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x - 1, y: p.y + 1}];
                    } else if (type === 44) {
                        npos = [{x: p.x, y: p.y}, {x: p.x, y: p.y + 1}, {x: p.x, y: p.y - 1}, {x: p.x - 1, y: p.y - 1}];
                    }
                }
                // Z
                if (type > 50 && type < 60) {
                    type = type > 52 ? 51 : type;
                    if (type === 51) {
                        npos = [{x: p.x, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x, y: p.y - 1}, {x: p.x - 1, y: p.y - 1}];
                    } else if (type === 52) {
                        npos = [{x: p.x, y: p.y}, {x: p.x, y: p.y + 1}, {x: p.x + 1, y: p.y}, {x: p.x + 1, y: p.y - 1}];
                    }
                }
                // 反Z
                if (type > 60 && type < 70) {
                    type = type > 62 ? 61 : type;
                    if (type === 61) {
                        npos = [{x: p.x, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x, y: p.y - 1}, {x: p.x + 1, y: p.y - 1}];
                    } else if (type === 62) {
                        npos = [{x: p.x, y: p.y}, {x: p.x, y: p.y + 1}, {x: p.x - 1, y: p.y}, {x: p.x - 1, y: p.y - 1}];
                    }
                }
                // ⊥
                if (type > 70 && type < 80) {
                    type = type > 74 ? 71 : type;
                    if (type === 71) {
                        npos = [{x: p.x, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x, y: p.y - 1}];
                    } else if (type === 72) {
                        npos = [{x: p.x, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x, y: p.y + 1}, {x: p.x, y: p.y - 1}];
                    } else if (type === 73) {
                        npos = [{x: p.x, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x + 1, y: p.y}, {x: p.x, y: p.y + 1}];
                    } else if (type === 74) {
                        npos = [{x: p.x, y: p.y}, {x: p.x - 1, y: p.y}, {x: p.x, y: p.y + 1}, {x: p.x, y: p.y - 1}];
                    }
                }
                return {npos: npos, type: type};
            };

            this.start = function () {
                newBlock();
                paintTimer();
            }
        }

        function init() {
            var game = new Game(document.getElementById("gc"));

            document.body.onkeydown = function (e) {
                switch (e.code.toLowerCase()) {
                    case "space":
                        game.move(4);
                        break
                    case "arrowup":
                        game.rotate();
                        break;
                    case "arrowdown":
                        game.move(1, true);
                        break
                    case "arrowleft":
                        game.move(2);
                        break;
                    case "arrowright":
                        game.move(3);
                        break;
                }
            }

            game.start();
        }
    </script>
</head>
<body onload="init()">
<div id="gc" style="width: 400px;height: 100%;margin: auto;background-color: #323234">

</div>
</body>
</html>